package com.starview.star.operators;

import java.io.File;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

import com.starview.platform.data.EvaluationException;
import com.starview.platform.data.IMap;
import com.starview.platform.data.IValue;
import com.starview.platform.data.type.IType;
import com.starview.platform.data.type.ITypeDescription;
import com.starview.platform.data.type.Location;
import com.starview.platform.data.type.Refresher;
import com.starview.platform.data.type.StandardTypes;
import com.starview.platform.data.type.TypeAlias;
import com.starview.platform.data.type.TypeDescription;
import com.starview.platform.data.type.TypeUtils;
import com.starview.platform.data.type.TypeVar;
import com.starview.platform.data.value.Array;
import com.starview.platform.data.value.BigNumWrap;
import com.starview.platform.data.value.BinaryWrap;
import com.starview.platform.data.value.BoolWrap;
import com.starview.platform.data.value.CharWrap;
import com.starview.platform.data.value.Cons;
import com.starview.platform.data.value.FloatWrap;
import com.starview.platform.data.value.IntWrap;
import com.starview.platform.data.value.LongWrap;
import com.starview.platform.data.value.NTuple;
import com.starview.platform.data.value.Relation;
import com.starview.platform.data.value.ResourceURI;
import com.starview.platform.data.value.StringWrap;
import com.starview.platform.data.value.VoidWrap;
import com.starview.star.compiler.cafe.type.CafeTypeDescription;
import com.starview.star.compiler.standard.StandardNames;
import com.starview.star.compiler.type.BuiltinInfo;
import com.starview.star.compiler.type.Dict;
import com.starview.star.compiler.util.LayeredHash;
import com.starview.star.compiler.util.LayeredMap;
import com.starview.star.operators.arith.Arithmetic;
import com.starview.star.operators.arith.BigNumUnary;
import com.starview.star.operators.arith.BoolCompare;
import com.starview.star.operators.arith.FloatBinary;
import com.starview.star.operators.arith.FloatCompare;
import com.starview.star.operators.arith.FloatTrig;
import com.starview.star.operators.arith.FloatUnary;
import com.starview.star.operators.arith.IntBinary;
import com.starview.star.operators.arith.IntCompare;
import com.starview.star.operators.arith.IntUnary;
import com.starview.star.operators.arith.IntegerBitString;
import com.starview.star.operators.arith.LongBinary;
import com.starview.star.operators.arith.LongBitString;
import com.starview.star.operators.arith.LongCompare;
import com.starview.star.operators.arith.LongUnary;
import com.starview.star.operators.arith.Number2Number;
import com.starview.star.operators.arith.NumericWrapper;
import com.starview.star.operators.arrays.ArrayOps;
import com.starview.star.operators.assignment.runtime.AtomicCell;
import com.starview.star.operators.assignment.runtime.AtomicInt;
import com.starview.star.operators.assignment.runtime.RefCell;
import com.starview.star.operators.ast.AstOperators;
import com.starview.star.operators.binary.BinaryCoercion;
import com.starview.star.operators.binary.BinaryEquality;
import com.starview.star.operators.general.General;
import com.starview.star.operators.hash.HashTreeOps;
import com.starview.star.operators.io.BasicIo;
import com.starview.star.operators.misc.MiscOps;
import com.starview.star.operators.relation.Extend;
import com.starview.star.operators.relation.RelationIterOps;
import com.starview.star.operators.relation.RelationOps;
import com.starview.star.operators.spawn.SpawnOps;
import com.starview.star.operators.string.CharOps;
import com.starview.star.operators.string.DateOps;
import com.starview.star.operators.string.DisplayValue;
import com.starview.star.operators.string.KeyGen;
import com.starview.star.operators.string.Number2String;
import com.starview.star.operators.string.RegexpOps;
import com.starview.star.operators.string.String2Number;
import com.starview.star.operators.string.StringCompare;
import com.starview.star.operators.string.StringOps;
import com.starview.star.operators.string.StringWrappers;
import com.starview.star.operators.system.BinaryWrappers;
import com.starview.star.operators.system.Clock;
import com.starview.star.operators.system.GStopHere;
import com.starview.star.operators.system.ShowDates;
import com.starview.star.operators.system.SystemUtils;
import com.starview.star.operators.uri.URIOps;

@SuppressWarnings("serial")
public class Intrinsics extends Dict
{
  private static final Intrinsics intrinsics = new Intrinsics();

  private final LayeredMap<String, ICafeBuiltin> builtins = new LayeredHash<String, ICafeBuiltin>();

  static {
    intrinsics.defineType(new TypeDescription(StandardTypes.rawBoolType));
    intrinsics.defineType(new TypeDescription(StandardTypes.rawCharType));
    intrinsics.defineType(new TypeDescription(StandardTypes.rawIntegerType));
    intrinsics.defineType(new TypeDescription(StandardTypes.rawLongType));
    intrinsics.defineType(new CafeTypeDescription(StandardTypes.rawFloatType, Double.class.getCanonicalName()));
    intrinsics.defineType(new CafeTypeDescription(StandardTypes.rawStringType, String.class.getCanonicalName()));
    intrinsics.defineType(new CafeTypeDescription(StandardTypes.rawDecimalType, BigDecimal.class.getCanonicalName()));
    intrinsics.defineType(new CafeTypeDescription(StandardTypes.fileType, File.class.getCanonicalName()));
    intrinsics.defineType(new CafeTypeDescription(StandardTypes.voidType, IValue.class.getCanonicalName()));
    intrinsics.defineType(new CafeTypeDescription(StandardTypes.voidType, Object.class.getCanonicalName()));
    intrinsics.defineType(new CafeTypeDescription(StandardTypes.anyType, IValue.class.getCanonicalName()));
    intrinsics.defineType(new CafeTypeDescription(Refresher
        .generalize(TypeUtils.hashType(new TypeVar(), new TypeVar())), IMap.class.getCanonicalName()));
    intrinsics.defineType(new CafeTypeDescription(Refresher.generalize(TypeUtils.relationType(new TypeVar())),
        Relation.class.getCanonicalName()));

    // Define standard types
    VoidWrap.declare(intrinsics);
    BoolWrap.declare(intrinsics);
    CharWrap.declare(intrinsics);
    StringWrap.declare(intrinsics);
    BinaryWrap.declare(intrinsics);
    Location.declare(intrinsics);
    ResourceURI.declare(intrinsics);
    RefCell.declare(intrinsics);
    AtomicCell.declare(intrinsics);
    AtomicInt.declare(intrinsics);
    Cons.declare(intrinsics);
    Array.declare(intrinsics);
    ArrayOps.declare(intrinsics);
    EvaluationException.declare(intrinsics);

    IntWrap.declare(intrinsics);
    LongWrap.declare(intrinsics);
    FloatWrap.declare(intrinsics);
    BigNumWrap.declare(intrinsics);

    NumericWrapper.declare(intrinsics);
    StringWrappers.declare(intrinsics);
    BinaryWrappers.declare(intrinsics);

    LongBitString.declare(intrinsics);
    IntegerBitString.declare(intrinsics);

    Arithmetic.declare(intrinsics);
    BigNumUnary.declare(intrinsics);
    SystemUtils.declare(intrinsics);
    Clock.declare(intrinsics);
    DisplayValue.declare(intrinsics);
    Extend.declare(intrinsics);
    FloatBinary.declare(intrinsics);
    FloatCompare.declare(intrinsics);
    FloatUnary.declare(intrinsics);
    FloatTrig.declare(intrinsics);
    General.declare(intrinsics);
    IntBinary.declare(intrinsics);
    IntCompare.declare(intrinsics);
    IntUnary.declare(intrinsics);
    KeyGen.declare(intrinsics);
    // LogLevel.declare(intrinsics);
    // LogMsgs.declare(intrinsics);
    LongBinary.declare(intrinsics);
    LongCompare.declare(intrinsics);
    LongUnary.declare(intrinsics);
    HashTreeOps.declare(intrinsics);
    MiscOps.declare(intrinsics);
    Number2Number.declare(intrinsics);
    BinaryEquality.declare(intrinsics);
    BinaryCoercion.declare(intrinsics);
    RegexpOps.declare(intrinsics);
    RelationOps.declare(intrinsics);
    RelationIterOps.declare(intrinsics);
    ShowDates.declare(intrinsics);
    BasicIo.declare(intrinsics);
    SpawnOps.declare(intrinsics);
    BoolCompare.declare(intrinsics);
    CharOps.declare(intrinsics);
    StringOps.declare(intrinsics);
    String2Number.declare(intrinsics);
    StringCompare.declare(intrinsics);
    DateOps.declare(intrinsics);
    Number2String.declare(intrinsics);
    URIOps.declare(intrinsics);

    AstOperators.declare(intrinsics);

    NTuple.declare(intrinsics);

    GStopHere.declare(intrinsics);

    Location noWhere = Location.nullLoc;
    intrinsics.defineTypeAlias(noWhere, new TypeAlias(Location.nullLoc, TypeUtils.typeExp(StandardNames.ALIAS,
        TypeUtils.typeExp("double"), StandardTypes.floatType)));
    intrinsics.defineTypeAlias(noWhere, new TypeAlias(Location.nullLoc, TypeUtils.typeExp(StandardNames.ALIAS,
        TypeUtils.typeExp("arbitrary"), StandardTypes.decimalType)));
  }

  private Intrinsics()
  {
    super(null);
  }

  /**
   * Declare a built-in function to the type context.
   * 
   * Although an instance value is passed to the {@code declareBuiltin} method, this value is NOT
   * used in actual code. It MUST be the case that a new copy of the passed-in built-in is
   * sufficient for execution.
   * 
   * I.e., the following code fragments MUST be equivalent:
   * 
   * <pre>
   * XX = builtin.enter(X, Y);
   * </pre>
   * 
   * and
   * 
   * <pre>
   * Class<? extends ICafeBuiltin> klass = builtin.getClass();
   *   ...
   * ICafeBuiltin nFun = klass.newInstance();
   *   ...
   * XX = nFun.enter(X,Y)
   * </pre>
   * 
   * @param builtin
   *          an instance of the built-in function object.
   */

  public void declareBuiltin(ICafeBuiltin builtin)
  {
    String name = builtin.getName();
    assert !builtins.containsKey(name);
    builtins.put(name, builtin);
    declareVar(name, new BuiltinInfo(builtin));
  }

  /**
   * Retrieve a built-in operator associated with a given name.
   * 
   * @param name
   * @return the built-in function object (if it exists) associated with a given name
   */

  public ICafeBuiltin getBuiltinOperator(String name)
  {
    return builtins.get(name);
  }

  public boolean isBuiltin(String name)
  {
    return builtins.containsKey(name);
  }

  public static Collection<? extends ICafeBuiltin> allBuiltins()
  {
    return intrinsics.builtins.values();
  }

  public static Collection<ITypeDescription> builtinTypes()
  {
    return intrinsics.getAllTypes().values();
  }

  public static ICafeBuiltin getBuiltin(String name)
  {
    return intrinsics.getBuiltinOperator(name);
  }

  public static boolean isIntrinsicType(String name)
  {
    return intrinsics.getTypeDescription(name) != null;
  }

  public static ITypeDescription intrinsicType(String name)
  {
    return intrinsics.getTypeDescription(name);
  }

  public static Intrinsics intrinsics()
  {
    return intrinsics;
  }

  public static Map<String, IType> standardTypes()
  {
    Map<String, IType> standards = new TreeMap<>();

    for (Entry<String, ITypeDescription> entry : intrinsics.getAllTypes().entrySet()) {
      standards.put(entry.getKey(), entry.getValue().getType());
    }

    return standards;
  }
}
